﻿namespace FormCreator.Model
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Xml;
    using WPForms.Common;
    using WPForms.Common.Model;

    /// <summary>
    /// Static helper class, which provides methods to create forminstance from xaml-s.
    /// </summary>
    public static class FormXamlFactory
    {
        /// <summary>
        /// Creates a <see cref="FormInstance"/> from XAML.
        /// </summary>
        /// <param name="xamlStream"><see cref="Stream"/> that contains the XAML declaration of the catalog.</param>
        /// <returns>An instance of <see cref="ModuleCatalog"/> built from the XAML.</returns>
        public static FormInstance CreateFromXaml(Stream xamlStream)
        {
            if (xamlStream == null)
            {
                throw new ArgumentNullException("xamlStream");
            }

            var loadedObject = (FormInstance)XamlReader.Load(xamlStream);
            GenerateKeysForForm(loadedObject);
            return loadedObject;
        }

        /// <summary>
        /// Creates a <see cref="FormInstance"/> from a XAML included as an Application Resource.
        /// </summary>
        /// <param name="builderResourceUri">Relative <see cref="Uri"/> that identifies the XAML included as an Application Resource.</param>
        /// <returns>An instance of <see cref="ModuleCatalog"/> build from the XAML.</returns>
        public static FormInstance CreateFromXaml(Uri builderResourceUri)
        {
            var streamInfo = System.Windows.Application.GetResourceStream(builderResourceUri);

            if ((streamInfo != null) && (streamInfo.Stream != null))
            {
                return CreateFromXaml(streamInfo.Stream);
            }

            return null;
        }

        /// <summary>
        /// Writes the given form to the xal file at the given path.
        /// </summary>
        /// <param name="path">Path of the xaml to create.</param>
        /// <param name="form">The form to save.</param>
        public static void WriteToXaml(string path, FormInstance form)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException("path");
            }

            var types = new List<Type>();

            // get all types
            GetAllTypes(form.MainSection, types);
            types.Add(form.GetType());

            var typeNamespace = new Dictionary<Type, string>();             // the type-xaml namespace associations
            var namespaceDeclaration = new Dictionary<string, string>();    // the namespace - full namespace declaration associations

            // add the model namespace
            namespaceDeclaration.Add("model", "clr-namespace:WPForms.Common.Model;assembly=WPForms.Common");

            // create the namespace declarations, and associate namespaces to types.
            foreach (var type in types)
            {
                var nsdecl = string.Format("clr-namespace:{0};assembly={1}", type.Namespace, type.Assembly.GetName().Name);
                string ns = null;
                if (!namespaceDeclaration.Values.Contains(nsdecl))
                {
                    ns = "my" + namespaceDeclaration.Count();
                    namespaceDeclaration.Add(ns, nsdecl);
                }
                else
                {
                    ns = namespaceDeclaration.Single(n => n.Value == nsdecl).Key;
                }

                typeNamespace[type] = ns;
            }

            using (var writer = new XmlTextWriter(path, System.Text.Encoding.UTF8))
            {
                writer.Namespaces = true;
                writer.WriteStartElement(GetXamlName(form, typeNamespace));

                // write all namespace declarations to the xaml
                foreach (var item in namespaceDeclaration)
                {
                    writer.WriteAttributeString("xmlns:" + item.Key, item.Value);
                }

                writer.WriteAttributeString("xmlns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
                WritePropertiesToXaml(writer, form);
                writer.WriteStartElement(GetXamlName(form, typeNamespace) + ".MainSection");
                WriteElementToXaml(writer, form.MainSection, typeNamespace);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Writes the given element to the xaml.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="element">The element to write.</param>
        /// <param name="namespaces">The list of xaml namespaces.</param>
        public static void WriteElementToXaml(XmlTextWriter writer, FormElement element, Dictionary<Type, string> namespaces)
        {
            writer.WriteStartElement(GetXamlName(element, namespaces));
            WritePropertiesToXaml(writer, element);
            var section = element as FormSection;
            if (section != null)
            {
                writer.WriteStartElement(GetXamlName(section, namespaces) + ".Children");
                foreach (var child in section.Children)
                {
                    WriteElementToXaml(writer, child, namespaces);
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }

        /// <summary>
        /// Returns the element's type name with the appropriate namespace prefix.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="namespaces">The list of namespaces.</param>
        /// <returns>
        /// The string which can be used in xaml to create this element.
        /// </returns>
        private static string GetXamlName(object element, Dictionary<Type, string> namespaces)
        {
            return namespaces[element.GetType()] + ":" + element.GetType().Name;
        }

        /// <summary>
        /// Gets all types in a form in a recursive manner.
        /// </summary>
        /// <param name="section">The current section, initially has to be the MainSection</param>
        /// <param name="current">The current set of types.</param>
        private static void GetAllTypes(FormSection section, List<Type> current)
        {
            if (!current.Contains(section.GetType()))
            {
                current.Add(section.GetType());
            }

            foreach (var child in section.Children)
            {
                var childSection = child as FormSection;
                if (childSection != null)
                {
                    GetAllTypes(childSection, current);
                }
                else
                {
                    if (!current.Contains(child.GetType()))
                    {
                        current.Add(child.GetType());
                    }
                }
            }
        }

        /// <summary>
        /// Writes the given element's properties to xaml.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="element">The element.</param>
        private static void WritePropertiesToXaml(System.Xml.XmlTextWriter writer, object element)
        {
            foreach (var property in element.GetType().GetProperties())
            {
                var description = property.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault(a => a is DescriptionAttribute) as DescriptionAttribute;
                if (description != null)
                {
                    var value = property.GetValue(element, null);
                    if (value != null)
                    {
                        writer.WriteAttributeString(property.Name, value.ToString());
                    }
                }
            }
        }

        /// <summary>
        /// Generates the keys for a form.
        /// </summary>
        /// <param name="form">The form.</param>
        private static void GenerateKeysForForm(FormInstance form)
        {
            if (string.IsNullOrEmpty(form.MainSection.Key))
            {
                GenerateKeysForSection(form.MainSection);
            }
        }

        /// <summary>
        /// Generates the keys for the given section.
        /// </summary>
        /// <param name="section">The section.</param>
        /// <param name="current">The current.</param>
        private static void GenerateKeysForSection(FormSection section, string current = null)
        {
            section.Key = current == null ? section.Name : current + "." + section.Name;
            int idx = 0;
            foreach (var child in section.Children)
            {
                if (child is FormSection)
                {
                    GenerateKeysForSection((FormSection)child, section.Key);
                }
                else
                {
                    child.Key = section.Key + "." + child.GetType().Name + idx;
                    idx++;
                }
            }
        }
    }
}
